#ifndef __MATH_UTILS_H__
#define __MATH_UTILS_H__

#define _USE_MATH_DEFINES

#include <math.h>
#include <algorithm>
#include <cstdint>

namespace PocketSurvival::Engine
{
    namespace MathUtils
    {
        constexpr inline float GetPI()
        {
            return M_PI;
        }

        template<typename T>
        constexpr inline T Clamp(T x, T min, T max)
        {
            if(x >= min)
            {
                if(x <= max)
                {
                    return x;
                }
                return max;
            }
            return min;
        }

        template<typename T>
        constexpr inline T Sign(T value)
        {
            if(value < 0)
            {
                return -1;
            }
            if(value > 0)
            {
                return 1;
            }
            return 0;
        }

        template<typename T>
        constexpr inline T Min(T value1, T value2)
        {
            if(value1 > value2)
            {
                return value2;
            }
            return value1;

            // return std::min(value1, value2);
        }

        template<typename T>
        constexpr inline T Min(T value1, T value2, T value3)
        {
            return Min<T>(Min<T>(value1, value2), value3);
        }

        template<typename T>
        constexpr inline T Min(T value1, T value2, T value3, T value4)
        {
            return Min<T>(Min<T>(Min<T>(value1, value2), value3), value4);
        }

        template<typename T>
        constexpr inline T Max(T value1, T value2)
        {
            if(value1 > value2)
            {
                return value1;
            }
            return value2;

            // return std::max(value1, value2);
        }

        template<typename T>
        constexpr inline T Max(T value1, T value2, T value3)
        {
            return Max<T>(Max<T>(value1, value2), value3);
        }

        template<typename T>
        constexpr inline T Max(T value1, T value2, T value3, T value4)
        {
            return Max<T>(Max<T>(Max<T>(value1, value2), value3), value4);
        }

        constexpr inline float Lerp(float x1, float x2, float f)
		{
			return x1 + (x2 - x1) * f;
		}

		constexpr inline float Log(float x)
		{
            return std::log(x);
		}

        template<typename T>
        constexpr inline T Sqrt(T value)
        {
            return std::sqrt(value);
        }

        template<typename T>
        constexpr inline T Sin(T value)
        {
            return std::sin(value);
        }

        template<typename T>
        constexpr inline T Cos(T value)
        {
            return std::cos(value);
        }

        template<typename T>
        constexpr inline T Acos(T value)
        {
            return std::acos(value);
        }

        template<typename T>
        constexpr inline T Asin(T value)
        {
            return std::asin(value);
        }

        template<typename T>
        constexpr inline T Atan2(T value,T value2)
        {
            return std::atan2(value, value2);
        }

        template<typename T>
		constexpr inline T Sqr(T x)
		{
			return x * x;
		}

        template<typename T>
		constexpr inline T Floor(T x)
		{
			return std::floor(x);
		}

        template<typename T>
        constexpr inline T Exp(T x)
        {
            return std::exp(x);
        }

        template<typename T>
        constexpr inline T Abs(T x)
        {
            return std::abs(x);
        }

        constexpr inline float Pow(float x, float n)
		{
			return std::pow(x, n);
		}
        constexpr inline float PowSign(float x, float n)
        {
            return Sign(x) * Pow(Abs<float>(x), n);
        }

		constexpr inline float Saturate(float x)
		{
			if (!(x < 0.0f))
			{
				if (!(x > 1.0f))
				{
					return x;
				}
				return 1.0f;
			}
			return 0.0f;
		}

        constexpr inline float Round(float x)
        {
            return (float)std::round(x);
        }
		constexpr inline float Remainder(float x, float y)
		{
			return x - MathUtils::Floor<float>(x / y) * y;
		}

        constexpr inline float Sigmoid(float x, float steepness)
        {
            if(x <= 0.0f)
            {
                return 0.0f;
            }
            if(x >= 1.0f)
            {
                return 1.0f;
            }
            float num = Exp<float>(steepness);
            float num2 = Exp<float>(2.0f * steepness * x);
            return num * (num2 - 1.0f) / ((num - 1.0f) * (num2 + num));
        }
        
        constexpr inline float DegToRad(float degress)
        {
            return degress / 180.0f * GetPI();
        }

        template<typename T>
        constexpr inline T Ceil(T x)
		{
			return std::ceil(x);
		}

        constexpr inline uint32_t Hash(uint32_t key)
        {
			key ^= key >> 16;
			key *= 2146121005;
			key ^= key >> 15;
			key = (uint32_t)((int32_t)key * -2073254261);
			key ^= key >> 16;
            return key;
        }

        template<typename T>
        constexpr inline uint32_t MinIndex(T *array, uint32_t len)
        {
            uint32_t minIndex = 0;
            for(uint32_t index = 1; index < len; ++index)
            {
                if(array[minIndex] > array[index])
                {
                    minIndex = index;
                }
            }
            return minIndex;
        }
        template<typename T>
        constexpr inline uint32_t MaxIndex(T *array, uint32_t len)
        {
            uint32_t maxIndex = 0;
            for(uint32_t index = 1; index < len; ++index)
            {
                if(array[maxIndex] < array[index])
                {
                    maxIndex = index;
                }
            }
            return maxIndex;
        }

        constexpr inline float LinearStep(float zero, float one, float f)
		{
			return Saturate((f - zero) / (one - zero));
		}

        constexpr inline float NormalizeAngle(float angle)
        {
            angle = (float)std::fmod(angle, 6.2831854820251465);
            if (angle > GetPI())
            {
                angle -= GetPI() * 2.0f;
            }
            else if (angle <= -GetPI())
            {
                angle += GetPI() * 2.0f;
            }
            return angle;
        }


    } // namespace MathUtils
} // namespace PocketSurvival::Engine

#endif